package com.simba.server.components;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.simba.common.frameserver.FrameChannelManager;
import com.simba.common.jsonserver.JsonChannelManager;
import com.simba.common.utils.StringUtil;
import com.simba.server.base.CommonUtil;
import com.simba.server.components.data.SorterInfo;
import org.apache.log4j.Logger;


import com.simba.common.log.LogUtil;
import com.simba.server.ServerManager;

import com.simba.server.commands.impl.fetch.FetchSlotInfoCmd;
import com.simba.server.commands.impl.fetch.FetchTrashSlotsCmd;
import com.simba.server.commands.impl.web.WebBoxInfoCmd;
import com.simba.server.commands.impl.web.WebSlotStatusCmd;
import com.simba.server.components.data.BoxInfo;
import com.simba.server.components.data.SlotInfo;
import com.simba.server.components.data.SortInfo;
import com.simba.server.task.BoxInfoAllocTask;
import com.simba.server.task.SlotStatusTask;
import org.jboss.netty.channel.Channel;
import org.json.JSONObject;

public class SlotManager
{
	private static class SlotManagerHolder
	{
		private static final SlotManager INSTANCE = new SlotManager();
	}

	private static final Logger logger = Logger.getLogger(SlotManager.class);

	public static SlotManager getInstance()
	{
		return SlotManagerHolder.INSTANCE;
	}

	private final BoxInfoAllocTask boxInfoAllocTask = new BoxInfoAllocTask();
	private final ConcurrentHashMap<String, SlotInfo> slotInfoList = new ConcurrentHashMap<>();

	private byte[] slotLockStatus;
	private final SlotStatusTask slotStatusTask = new SlotStatusTask();
	private final ConcurrentHashMap<String, SlotInfo> trashSlotList = new ConcurrentHashMap<String, SlotInfo>();
	private final Map<Integer, List<Integer>> trashSlotListByType = new ConcurrentHashMap<>();
	private Channel slotScreenChannel;
	private Channel bigScreenChannel;

	private SlotManager()
	{
	}

	public Boolean checkSlotIsTrash(String slotId)
	{
		Boolean ret = false;
		if (this.trashSlotList.containsKey(slotId))
		{
			ret = true;
		}

		return ret;
	}

	public void clearSlotInfoList()
	{
		this.slotInfoList.clear();
	}

	public boolean containsSlotInfo(String key)
	{
		return this.slotInfoList.containsKey(key);
	}

	public String createSlotBindUUID()
	{
		return "";
	}

	public void doAllocBoxInfo(SlotInfo slotInfo)
	{
		synchronized (slotInfo)
		{
			/**
			 * Double Check
			 */
			if (slotInfo.getAllocBoxInfo())
			{
				slotInfo.setLockStatus(SortCommonDefines.SLOT_SOFT_UNLOCK_FLAG);
				this.setSlotSoftLockStatus(slotInfo);
				this.sendSlotSoftUnLockToPLC(slotInfo);

				CommandExecutor.executorWebCommand(WebSlotStatusCmd.NAME, slotInfo,false);
				return;
			}

		    boolean ret = CommandExecutor.executorWebCommand(WebBoxInfoCmd.NAME, slotInfo,false);
			/**
			 * 分配箱号失败
			 */
			if (!ret)
			{
				LogUtil.LogDebug(SlotManager.logger,
						"[SlotManager.doAllocBoxInfo] Fetch Box Info Failed, SlotId : ["
								+ slotInfo.getSlotId() + "]");
				slotInfo.resetBoxInfo();
			}
			else
			{
				slotInfo.setAllocBoxInfo(true);
				slotInfo.setLockStatus(SortCommonDefines.SLOT_SOFT_UNLOCK_FLAG);
				this.setSlotSoftLockStatus(slotInfo);
				this.sendSlotSoftUnLockToPLC(slotInfo);

				CommandExecutor.executorWebCommand(WebSlotStatusCmd.NAME, slotInfo,false);
			}
		}
	}

	public SlotInfo getSlotInfo(String key)
	{
		return this.slotInfoList.get(key.toLowerCase());
	}

	public int getSlotInfoCount()
	{
		return this.slotInfoList.size();
	}

	public SlotInfo[] getSlotInfoList()
	{
		return this.slotInfoList.values().toArray(new SlotInfo[0]);
	}

	public byte[] getSlotLockStatus()
	{
		return this.slotLockStatus;
	}

	public List<Integer> getTrashSlots(int errorCode)
	{
		return trashSlotListByType.getOrDefault(errorCode, null);
	}

	public void init()
	{
		try
		{
			int slotSize = MachineParams.getInstance().getSlotNum();
			this.slotLockStatus = new byte[slotSize];
			for (int i = 0; i < this.slotLockStatus.length; i++)
			{
				this.slotLockStatus[i] = 0x01;
			}

			this.initSlotInfos();
			this.initTrashSlots();
			this.initTasks();
		}
		catch (Exception ex)
		{
			LogUtil.LogError(SlotManager.logger, "init slot info  error: " + ex);
		}
	}

	public void initTrashSlots()
	{
		this.trashSlotList.clear();
		this.trashSlotListByType.clear();
		this.doGetTrashSlots();
	}

	public void offerSlotPackToRestore(SlotInfo slotInfo)
	{
		try
		{
			SlotInfo newSlotInfo = new SlotInfo();
			newSlotInfo.cloneSlotInfo(slotInfo);
			ServerManager.getInstance().getDataRestoreTask().offerRestoreDataToQueue(newSlotInfo);
		}
		catch (Exception e)
		{
			LogUtil.LogError(SlotManager.logger, "offer slot info to restore: " + e.getMessage());
		}
	}

	public void parseSlotFromBarcode(SortInfo sortInfo)
	{
		try
		{
			String barcode = sortInfo.getBarcode();
			String[] info = barcode.split("-");
			if (info.length < 4)
			{
				int errorCode = SortCommonDefines.ERROR_CODE_11_POST_FAIL_SLOT_ERROR;
				sortInfo.setErrorCodeSVR(errorCode);
				sortInfo.getSlotsList().clear();
			}
			else
			{
				sortInfo.getSlotsList().clear();

				String slotIdKey = info[3];
				SlotInfo slotInfo = SlotManager.getInstance().getSlotInfo(slotIdKey);
				String boxCode = slotInfo.getBoxInfo().getBoxCode();
				if (boxCode != null && !boxCode.equals(""))
				{
					sortInfo.getSlotsList().add(Integer.parseInt(slotIdKey));
				}
			}
		}
		catch (Exception e)
		{
			int errorCode = SortCommonDefines.ERROR_CODE_11_POST_FAIL_SLOT_ERROR;
			sortInfo.setErrorCodeSVR(errorCode);
			sortInfo.getSlotsList().clear();
			LogUtil.LogError(SlotManager.logger,
					"parse slot from barcode error: " + e.getMessage());
		}

	}

	public void postInitProcess()
	{
	}

	public void registerTrashSlot()
	{
		for (List<Integer> slotValue : trashSlotListByType.values())
		{
			for (Integer slot : slotValue)
			{
				String slotId = String.valueOf(slot);
				if (this.trashSlotList.containsKey(slotId))
				{
					continue;
				}

				SlotInfo slotInfo = this.slotInfoList.get(slotId);
				if (slotInfo != null)
				{
					this.trashSlotList.put(slotId, slotInfo);
				}
			}
		}
	}

	public void reloadTshSlots()
	{
		this.initTrashSlots();
	}

	public SlotInfo removeSlotInfo(String key)
	{
		return this.slotInfoList.remove(key);
	}

	public void registerSlotScreen(Channel channel){
		this.slotScreenChannel = channel;
	}

	public void registerBigScreen(Channel channel){
		this.bigScreenChannel = channel;
	}



	private Map<String, Object> createSlotScreenInfo(SlotInfo info){
		Map<String, Object> responseMap = new HashMap<>();
		responseMap.put(SortCommonDefines.JSON_SERVICE_NAME, "screenSlot");
		responseMap.put("slot",info.getSlotId());
		responseMap.put("route",info.getArriveCode());
		responseMap.put("direction",info.getSlotName());
		responseMap.put("currentCount",info.getCurrentMailCount());
		responseMap.put("totalCount",info.getTotalMailCount());

		return responseMap;
	}

	private Map<String, Object> createBigScreenInfo(String slotId,String slotName, int mailCount, boolean isLock){
		Map<String, Object> responseMap = new HashMap<>();
		if (isLock){
			responseMap.put(SortCommonDefines.JSON_SERVICE_NAME, "SlotLock");
			responseMap.put("des",slotName);
			responseMap.put("mailCount",mailCount);
		}else {
			responseMap.put(SortCommonDefines.JSON_SERVICE_NAME, "SlotUnLock");
		}
		responseMap.put("slot",slotId);

		return responseMap;
	}

	public void sendSlotInfoToScreen(String slotId){
		if (slotScreenChannel==null){
			return;
		}

		if (!slotScreenChannel.isConnected()){
			LogUtil.LogError(SlotManager.logger, "Send slot info to slot screen failure: not connected");
			return;
		}
		if (StringUtil.isNull(slotId)){
			for (SlotInfo info : SlotManager.getInstance().getSlotInfoList()) {
				String jsonObject = CommonUtil.getGson().toJson(createSlotScreenInfo(info));
				JsonChannelManager.getInstance().commitResponse(slotScreenChannel, jsonObject.getBytes(), false);
			}
		}else {
			SlotInfo info = SlotManager.getInstance().getSlotInfo(slotId);
			if (info!=null){
				String jsonObject = CommonUtil.getGson().toJson(createSlotScreenInfo(info));
				JsonChannelManager.getInstance().commitResponse(slotScreenChannel, jsonObject.getBytes(), false);
			}

		}


	}

	public void sendSlotPackInfoToScreen(String slotId, int mailCount, boolean isLock){
		if (bigScreenChannel==null){
			return;
		}

		if (!bigScreenChannel.isConnected()){
			LogUtil.LogError(SlotManager.logger, "Send slot pack info to big screen failure: not connected");
			return;
		}
		if (StringUtil.isNull(slotId)){
			LogUtil.LogError(SlotManager.logger, "Send slot pack info to big screen failure: slot id can't be null");
			return;
		}

		SlotInfo info = SlotManager.getInstance().getSlotInfo(slotId);
		if (info!=null){
			String jsonObject = CommonUtil.getGson().toJson(createBigScreenInfo(slotId, info.getSlotName(), mailCount,  isLock));
			JsonChannelManager.getInstance().commitResponse(bigScreenChannel, jsonObject.getBytes(), false);
		}
	}

	public void renewSlotInfo(){
		for (SlotInfo info : getSlotInfoList()) {
			info.setTotalMailCount(0);
			info.setCurrentMailCount(0);
		}
	}

	public void sendSlotSoftLockToPLC(SlotInfo slotInfo)
	{
		try
		{
			int sorterId = slotInfo.getSorterId();
			SorterInfo wheelSorterInfo = SorterManager.getInstance().getWheelSorterByID(String.valueOf(sorterId));
			if (wheelSorterInfo == null)
			{
				LogUtil.LogError(logger, "[sendSlotSoftLockToPLC] Sorter not found! Sorter ID:" + sorterId);
				return;
			}

			byte[] replyBuffer = SortProtocolCreator.getInstance().createSlotStatusLock(sorterId, Integer.parseInt(slotInfo.getSlotId()));
			if (wheelSorterInfo.getChannel()==null){
				LogUtil.LogError(logger, "[sendSlotSoftLockToPLC] Sorter channel not found! Sorter ID:" + sorterId);
				return;
			}
			FrameChannelManager.getInstance().commitResponse(wheelSorterInfo.getChannel(), replyBuffer, false);
		}
		catch (Exception e)
		{
			LogUtil.LogError(logger, "[sendSlotSoftLockToPLC] Found an exception:" + e.getMessage());
		}
	}

	public void sendSlotSoftUnLockToPLC(SlotInfo slotInfo)
	{
		try
		{
			int sorterId = slotInfo.getSorterId();
			SorterInfo wheelSorterInfo = SorterManager.getInstance().getWheelSorterByID(String.valueOf(sorterId));
			if (wheelSorterInfo == null)
			{
				LogUtil.LogError(logger, "[sendSlotSoftUnLockToPLC] Sorter not found! Sorter ID:" + sorterId);
				return;
			}

			byte[] replyBuffer = SortProtocolCreator.getInstance().createSlotStatusUnLock(sorterId, Integer.parseInt(slotInfo.getSlotId()));
			if (wheelSorterInfo.getChannel()==null){
				LogUtil.LogError(logger, "[sendSlotSoftUnLockToPLC] Sorter channel not found! Sorter ID:" + sorterId);
				return;
			}
			FrameChannelManager.getInstance().commitResponse(wheelSorterInfo.getChannel(), replyBuffer, false);
		}
		catch (Exception e)
		{
			LogUtil.LogError(logger, "[sendSlotSoftUnLockToPLC] Found an exception:" + e.getMessage());
		}
	}

	public void setSlotSoftLockStatus(SlotInfo slotInfo)
	{
		try
		{
			int slotIndex = Integer.parseInt(slotInfo.getSlotId()) - 1;
			int softLockStatus = slotInfo.getLockStatus();
			if (this.slotLockStatus == null || this.slotLockStatus.length <= slotIndex)
			{
				return;
			}

			this.slotLockStatus[slotIndex] = (byte) softLockStatus;
		}
		catch (Exception e)
		{
			LogUtil.LogError(SlotManager.logger, "Update Slot Soft Lock Status Error.");
		}
	}

	private void doGetTrashSlots()
	{
		Boolean ret = CommandExecutor.executorSqlCommand(FetchTrashSlotsCmd.NAME, trashSlotListByType,false);
		if (ret)
		{
			this.registerTrashSlot();
		}
	}

	private void initSlotInfos()
	{
		this.slotInfoList.clear();
		List<SlotInfo> slots = new LinkedList<>();
		Boolean ret =  CommandExecutor.executorSqlCommand(FetchSlotInfoCmd.NAME, slots,false);
		if (ret)
		{
			for (SlotInfo slotInfo : slots)
			{
				slotInfo.resetBoxInfo();
				this.slotInfoList.put(slotInfo.getSlotId(), slotInfo);
			}
		}

		this.printSlotInfos();
	}

	private void initTasks()
	{
		this.boxInfoAllocTask.start();
		this.slotStatusTask.start();
	}

	private void printSlotInfos()
	{
		String info = "[SlotManager.initSlotInfos] Current slot number : ["
				+ this.slotInfoList.size() + "]";
		LogUtil.LogDebug(SlotManager.logger, info);
	}
}
